package socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;

/**
 * 聊天室服务端
 */
public class Server {
    /*
    java.net.ServerSocket;
    ServerSocket是运行在服务端的。它主要有两个作用
    1.向系统申请并打开服务端口，客户端可以通过这个端口与服务端建立连接
    2.监听服务端口，一旦一个客户端通过该端口与服务端建立连接则会船舰一个Socket，通过他与客户端进行交互
    如果我们把Socket比喻成电话，那么ServerSocket就相当于总机
     */
    private ServerSocket serverSocket;
    //用来保存所有客户端输出流的数组，用于让ClientHandler之间共享输出流广播消息使用
//    private PrintWriter[] allOut = {};
//    private Collection<PrintWriter> allOut = new ArrayList<>();
    //基于ArrayList创建一个并发安全的集合，存放所有输出流
    private List<PrintWriter> allOut = Collections.synchronizedList(new ArrayList<>());
    public Server() {
        try {
            /*
            实例化ServerSocket是要指定服务端口，如果该端口已经被系统其他程序使用时，会抛出异常：
            java.net.BindException:address already in use
            此时需要更换一个端口在尝试启动，或者将占用该端口的程序关闭后再尝试启动当前程序
             */
            System.out.println("正在启动服务端。。。");
            serverSocket = new ServerSocket(8088);
            System.out.println("启动服务端完毕！");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void start(){
        try {
            /*
            ServerSocket提供的方法：Socket accept（）
            该方法是一个阻塞方法，调用后程序就卡住了，此时开始等待客户端的连接，一旦一个客户端建立连接，
            此时accept方法会立即返回一个Socket实例，通过这个Socket就可以与连接的客户端进行交互了
             */
            while(true) {
                System.out.println("等待客户端连接...");
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端连接了！");
                //启动一个线程处理与该客户端的交互,来一个客户端就启动一个线程，每个客户端都有一个线程并发运行
                Runnable handler= new ClientHandler(socket);//把socket传送到ClientHandler里面
                Thread t = new Thread(handler);
                t.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }
    private class ClientHandler implements Runnable{
        private Socket socket;//先创建成员属性，让所有方法都能访问
        private String host;//当前客户端的IP地址信息
        public ClientHandler(Socket socket){//必须会！！创建构造方法，将外界的对象传到方法里面，再给成员属性赋值
            this.socket = socket;
            //通过socket获取远端计算机地址信息
            host = socket.getInetAddress().getHostAddress();
        }
        public void run() {
            PrintWriter pw = null;
            /*
            Socket 提供的方法：InputStream getInputStream()
            通过socket获取的输入流可以读取远端计算机发送过来的数据
             */
//            InputStream in = socket.getInputStream();
//            InputStreamReader isr = new InputStreamReader(in,"uft-8");
//            BufferedReader br = new BufferedReader(isr);
            try {
                    BufferedReader br = new BufferedReader(
                    new InputStreamReader(
                            socket.getInputStream(), "UTF-8"
                    )
            );
                //通过socket获取输出流用于给客户发送消息
                pw =new PrintWriter(
                        new BufferedWriter(
                                new OutputStreamWriter(
                                        socket.getOutputStream(),"UTF-8"
                                )
                        ),true
                );
                //将当前对应客户端的输出流存入到共享数组allOut中，以便广播消息
                //锁后面用this指的是每个线程运行自己的ClientHandler，且因为同步块中数组扩容会改变对象，用allOut也不行
//                synchronized (serverSocket) {//Server.class一般用在静态方法中，在这也能用
//                    //1.先对allOut数组扩容
//                    allOut = Arrays.copyOf(allOut, allOut.length + 1);
//                    //2.将当前pw存入数组最后一个位置
//                    allOut[allOut.length - 1] = pw;
//                }
                    allOut.add(pw);//如果当前集合是并发安全的集合，则不需要同步块控制了

//                System.out.println(host + "上线了！当前在线人数：" + allOut.length);
                System.out.println(host + "上线了！当前在线人数：" + allOut.size());
                String line;
                while ((line = br.readLine()) != null) {
                    String message = line;
                    System.out.println(host + "说：" + line);
//                    synchronized (serverSocket) {//与增删数组的锁形成互斥
                        //将消息发送给所有客户端
//                        for (int i = 0; i < allOut.length; i++) {
//                        allOut[i].println(host + "说：" + line);
//                    }
//                        for(PrintWriter p:allOut){
//                            p.println(host + "说：" + line);
//                        }
//                    }
                        //当使用并发安全的集合时，遍历要采取foreach方法
                        allOut.forEach(p->p.println(host+"说："+message));//匿名内部类
                }
            }catch(IOException e){
                e.printStackTrace();
            }finally {
                //处理该客户端断开连接后的操作
                //将对应当前客户端的输出流从共享数组allOut中删除
//                synchronized (serverSocket) {//三个锁形成互斥，锁定对象必须相同
//                    for (int i = 0; i < allOut.length; i++) {
//                        if (pw == allOut[i] ) {
//                            allOut[i] = allOut[allOut.length - 1];
//                            allOut = Arrays.copyOf(allOut, allOut.length - 1);
//                            break;
//                        }
//                    }
//                }
                    allOut.remove(pw);
//                System.out.println(host + "下线了！当前在线人数：" + allOut.length);
                System.out.println(host + "下线了！当前在线人数：" + allOut.size());
                try {
                    //最终不再通讯时要关闭socket（相当于关电话）
                    //socket关闭后，通过socket获取的输入流和输出流就自动关闭了
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
    }
}
